import os
import logging
import threading
import socket
import time
import json
 
from pyftpdlib._compat import PY3
from pyftpdlib._compat import FileNotFoundError
from pyftpdlib._compat import getcwdu
from pyftpdlib._compat import super
from pyftpdlib.authorizers import DummyAuthorizer
from pyftpdlib.handlers import FTPHandler
from pyftpdlib.handlers import _import_sendfile
from pyftpdlib.ioloop import IOLoop
from pyftpdlib.servers import FTPServer

from logging.handlers import RotatingFileHandler  

PASV_PORT = 8022
FTP_PORT = 8021
HOME = getcwdu()

# Attempt to use IP rather than hostname (test suite will run a lot faster)
try:
    HOST = socket.gethostbyname('localhost')
except socket.error:
    HOST = 'localhost'


GLOBAL_TIMEOUT = 2
BUFSIZE = 1024
INTERRUPTED_TRANSF_SIZE = 32768
NO_RETRIES = 5
VERBOSITY = 1 if os.getenv('SILENT') else 2

APPVEYOR = 'APPVEYOR' in os.environ
GITHUB_ACTIONS = 'GITHUB_ACTIONS' in os.environ or 'CIBUILDWHEEL' in os.environ

LOG_FMT = "[%(asctime)s] - %(levelname)s : %(message)s"

CI_TESTING = APPVEYOR or GITHUB_ACTIONS

if CI_TESTING:
    GLOBAL_TIMEOUT *= 3
    NO_RETRIES *= 3

logger = logging.getLogger('pyftpdlib')

def configure_logging():
    global logger
    logpath = os.environ.get("HOME") + '/log/ftpserver.log'
    file_handler = RotatingFileHandler(logpath, maxBytes=1024*1024)
    file_handler.setLevel(level=logging.WARNING)
    formatter = logging.Formatter(fmt=LOG_FMT)
    file_handler.setFormatter(formatter)
    
    logger.setLevel(logging.WARNING)
    logger.addHandler(file_handler)

    handler = logging.StreamHandler()
    handler.setFormatter(formatter)
    logger.setLevel(logging.WARNING)
    logger.addHandler(handler)
    

def setup_server(handler, server_class, user, passwd, path, perm, masquerade_address):
    address = ('', FTP_PORT)
    authorizer = DummyAuthorizer()
    # full perms
    authorizer.add_user(user, passwd, path, perm=perm)
    # authorizer.add_anonymous(HOME)
    handler.authorizer = authorizer
    handler.auth_failed_timeout = 0.001
    # lower buffer sizes = more "loops" while transferring data
    # = less false positives
    handler.banner = "Agv ftpd server ready."
    handler.passive_ports = [PASV_PORT] 
    if masquerade_address != '':
        handler.masquerade_address = masquerade_address
        print('ftp masquerade_address:', masquerade_address)

    handler.dtp_handler.ac_in_buffer_size = 4096
    handler.dtp_handler.ac_out_buffer_size = 4096
    server = server_class(address, handler)
    server.max_cons = 5
    server.max_cons_per_ip = 2
    return server


def assert_free_resources(parent_pid=None):
    # check orphaned threads
    ts = threading.enumerate()
    assert len(ts) == 1, ts
    # check orphaned process children
    this_proc = psutil.Process(parent_pid or os.getpid())
    children = this_proc.children()
    if children:
        warnings.warn(
            "some children didn't terminate %r" % str(children),
            UserWarning,
            stacklevel=2,
        )
        for child in children:
            try:
                child.kill()
                child.wait(GLOBAL_TIMEOUT)
            except psutil.NoSuchProcess:
                pass
    # check unclosed connections
    if POSIX:
        cons = [
            x
            for x in this_proc.connections('tcp')
            if x.status != psutil.CONN_CLOSE_WAIT
        ]
        if cons:
            warnings.warn(
                "some connections didn't close %r" % str(cons),
                UserWarning,
                stacklevel=2,
            )

def reset_server_opts():
    # Since all pyftpdlib configurable "options" are class attributes
    # we reset them at module.class level.
    import pyftpdlib.handlers
    import pyftpdlib.servers

    # Control handlers.
    tls_handler = getattr(
        pyftpdlib.handlers, "TLS_FTPHandler", pyftpdlib.handlers.FTPHandler
    )
    for klass in (pyftpdlib.handlers.FTPHandler, tls_handler):
        klass.auth_failed_timeout = 0.001
        klass.authorizer = DummyAuthorizer()
        klass.banner = "pyftpdlib ready."
        klass.masquerade_address = None
        klass.masquerade_address_map = {}
        klass.max_login_attempts = 3
        klass.passive_ports = None
        klass.permit_foreign_addresses = False
        klass.permit_privileged_ports = False
        klass.tcp_no_delay = hasattr(socket, 'TCP_NODELAY')
        klass.timeout = 300
        klass.unicode_errors = "replace"
        klass.use_gmt_times = True
        klass.use_sendfile = _import_sendfile() is not None
        klass.ac_in_buffer_size = 4096
        klass.ac_out_buffer_size = 4096
        if klass.__name__ == 'TLS_FTPHandler':
            klass.tls_control_required = False
            klass.tls_data_required = False

    # Data handlers.
    tls_handler = getattr(
        pyftpdlib.handlers, "TLS_DTPHandler", pyftpdlib.handlers.DTPHandler
    )
    for klass in (pyftpdlib.handlers.DTPHandler, tls_handler):
        klass.timeout = 300
        klass.ac_in_buffer_size = 4096
        klass.ac_out_buffer_size = 4096
    pyftpdlib.handlers.ThrottledDTPHandler.read_limit = 0
    pyftpdlib.handlers.ThrottledDTPHandler.write_limit = 0
    pyftpdlib.handlers.ThrottledDTPHandler.auto_sized_buffers = True

    # Acceptors.
    ls = [pyftpdlib.servers.FTPServer, pyftpdlib.servers.ThreadedFTPServer]
    ls.append(pyftpdlib.servers.MultiprocessFTPServer)

    for klass in ls:
        klass.max_cons = 0
        klass.max_cons_per_ip = 0
                           
class ftp_handler(FTPHandler):
    global logger
    HOST = '127.0.0.1'
    PORT = 10000
    
    def send_message(self, msg):  
        try:  
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:  
                s.settimeout(5)
                s.connect((self.HOST, self.PORT))  
                s.sendall(msg.encode('utf-8'))  
            logger.warning('send msg:' + msg)  
        except socket.error as e:  
            logger.warning('Connect error: ' + str(e))  
        except Exception as e:  
            logger.warning('Unknown error: ' + str(e))  

    def on_connect(self):
        logger.warning(self.remote_ip + ':' + str(self.remote_port) + ' connected!')
        data_dict = {'ip': self.remote_ip, 'port': str(self.remote_port), 'user': '', 'file': '', 'state': 3}  
        data_json = json.dumps(data_dict)    
        self.send_message(data_json)

    def on_disconnect(self):
        logger.warning('on_disconnect:' + self.remote_ip)
        data_dict = {'ip': self.remote_ip, 'port': str(self.remote_port), 'user': '', 'file': '', 'state': 2}  
        data_json = json.dumps(data_dict)    
        self.send_message(data_json)
        pass

    def on_login(self, username):
        logger.warning('on_login:' + username)
        data_dict = {'ip': self.remote_ip, 'port': str(self.remote_port), 'user': username, 'file': '', 'state': 4}  
        data_json = json.dumps(data_dict)    
        self.send_message(data_json)
        pass

    def on_logout(self, username):
        logger.warning('on_logout:' + username)
        data_dict = {'ip': self.remote_ip, 'port': str(self.remote_port), 'user': username, 'file': '', 'state': 5}
        data_json = json.dumps(data_dict)    
        self.send_message(data_json)
        pass

    def on_file_sent(self, file):
        logger.warning ('on_file_sent:' + file)
        data_dict = {'ip': self.remote_ip, 'port': str(self.remote_port), 'user': '', 'file': file, 'state': 7}
        data_json = json.dumps(data_dict)    
        self.send_message(data_json)
        pass

    def on_file_received(self, file):
        logger.warning('on_file_received:' + file)
        data_dict = {'ip': self.remote_ip, 'port': str(self.remote_port), 'user': '', 'file': file, 'state': 6}
        data_json = json.dumps(data_dict)    
        self.send_message(data_json)
        pass

    def on_incomplete_file_sent(self, file):
        msg = file
        logger.warning('on_incomplete_file_sent:' + file)
        data_dict = {'ip': self.remote_ip, 'port': str(self.remote_port), 'user': '', 'file': file, 'state': 9}
        data_json = json.dumps(data_dict)    
        self.send_message(data_json)
        pass

    def on_incomplete_file_received(self, file):
        msg = file
        logger.warning('on_incomplete_file_received:' + file)
        data_dict = {'ip': self.remote_ip, 'port': str(self.remote_port), 'user': '', 'file': file, 'state': 8}
        data_json = json.dumps(data_dict)    
        self.send_message(data_json)
        import os
        os.remove(file)

class ThreadedFTPd(threading.Thread):
    global logger
    handler = ftp_handler
    server_class = FTPServer
    poll_interval = 0.001 if CI_TESTING else 0.000001
    # Makes the thread stop on interpreter exit.
    daemon = True
     
    def __init__(self, user, passwd, path, perm, masquerade_address=''):
        self.parent_pid = os.getpid()
        super().__init__(name='pyftpdlib')
        self.server = setup_server(self.handler, self.server_class, user, passwd, path, perm, masquerade_address)
        
        self.lock = threading.Lock()
        self._stop_flag = False
        self._event_stop = threading.Event()

    def run(self):
        try:
            while not self._stop_flag:
                with self.lock:
                    self.server.serve_forever(
                        timeout=self.poll_interval, blocking=False
                    )
        finally:
            self._event_stop.set()

    def stop(self):
        self._stop_flag = True  # signal the main loop to exit
        self._event_stop.wait()
        self.server.close_all()
        self.join()
        # reset_server_opts()
        # assert_free_resources(self.parent_pid)
        logger.warning('server stop.')
        


def start_ftp(user, passwd, path, perm, masaddr, timeout=10):
    global logger  
    configure_logging()
    logger.warning('Start ftp server')
    if (timeout <= 0):
        logger.error('Error:error timeout:'+ str(timeout))
        return
    
    server_class = ThreadedFTPd(user, passwd, path, perm, masaddr)
 
    timer = threading.Timer(timeout, server_class.stop)   
    timer.start()

    server_class.start()
    server_class.join()


def stop_ftp():
    server_class.stop()
